package com.weilus.filters;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.PathContainer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;

/**
 * spring:
 *   cloud:
 *     gateway:
 *       authentication:
 *       - id: flux-call
 *         auth:
 *         #需要认证才能访问
 *         - GET /test/**=#{authenticated()}
 *         #认证且指定域权限才能访问
 *         - PUT /test/add=#{authenticated() && hasScopes('user-info') && hasRoles('aa,bb')}
 *         #认证且指定权限才能访问
 *         - /test/other=#{hasRoles('aa,bb')}
 */
@Component
@Slf4j
public class AuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticationGatewayFilterFactory.Config> {
    private AuthProperties properties;
    private ReactiveRedisAuthentication redisAuthentication;

    public AuthenticationGatewayFilterFactory(ReactiveRedisConnectionFactory factory,AuthProperties properties){
        super(Config.class);
        this.redisAuthentication = new ReactiveRedisAuthentication(factory);
        this.properties = properties;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            PathContainer path = PathContainer.parsePath(exchange.getRequest().getURI().getRawPath());
            HttpMethod method = exchange.getRequest().getMethod();
            String token  = Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("token")).orElse(exchange.getRequest().getQueryParams().getFirst("token"));
            Route route =(Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
            Set<AuthProperties.AuthMatcher> matches = properties.getMatchers(route.getId(),method,path);
            if(!CollectionUtils.isEmpty(matches)){
                if(StringUtils.isEmpty(token))return noPermit(exchange);
                return redisAuthentication
                        .loadAuth(token)
                        .filter(auth-> auth.sPel(matches))
                        .then(chain.filter(exchange))
                        .or(noPermit(exchange));
            } else {
                return chain.filter(exchange);
            }
        };
    }

    public Mono<Void> noPermit(ServerWebExchange exchange){
        ServerWebExchangeUtils.setResponseStatus(exchange,HttpStatus.FORBIDDEN);
        return exchange.getResponse().setComplete();
    }

    public static class Config {

    }
}
